package ordersubmit

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"mall/common/cachex"
	"mall/common/globalkey"
	"mall/common/moneyx"
	"mall/common/mqueuex"
	"mall/common/resultx"
	"mall/common/subscribex"
	"mall/common/uniqueidx"
	"mall/common/utils/pointy"
	"mall/service/forum/api/internal/svc"
	"mall/service/forum/api/internal/types"
	mqueueTypes "mall/service/mqueue/common/types"
	"mall/service/order/rpc/orderclient"
	"mall/service/user/rpc/userclient"

	"github.com/hibiken/asynq"
	"github.com/shopspring/decimal"
	"github.com/sirupsen/logrus"
	"github.com/wechatpay-apiv3/wechatpay-go/core"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
	"github.com/zeromicro/go-zero/core/logx"
)

// OrderHandler 写订单表handler
type OrderHandler struct {
	// 合成复用Next
	Next
}

// Do 写订单表
func (h *OrderHandler) Do(c *Context) (err error) {
	logx.Infow("写订单表...")
	if !c.Resp.StockValid || !c.Resp.AddressValid || !c.Resp.BalanceValid || !c.Resp.PromotionValid ||
		!c.Resp.ActivityValid || !c.Resp.CouponValid || !c.Resp.FinalValid {
		return
	}

	// 2.3. 创建订单
	var orderInfo orderclient.OrderInfo
	var orderSn string
	if c.OrderType == globalkey.OrderTypeVirtual { // 虚拟
		orderSn = uniqueidx.GenSn(uniqueidx.SN_PREFIX_GEM_ORDER)
	} else { // 普通实物
		orderSn = uniqueidx.GenSn(uniqueidx.SN_PREFIX_COMMON_ORDER)
	}
	orderInfo.UserId = pointy.GetPointer(c.UserId)
	orderInfo.UserRank = pointy.GetPointer(c.UserRank)
	orderInfo.Buyer = pointy.GetPointer(c.UserName)
	orderInfo.Sn = pointy.GetPointer(orderSn)
	orderInfo.OrderType = pointy.GetPointer(c.OrderType)
	orderInfo.SourceType = pointy.GetPointer(globalkey.ThirdPartyPlatformWechatMini)
	orderInfo.Number = c.Resp.Total
	orderInfo.GemAmount = c.Resp.GemAmount
	orderInfo.RetailAmount = c.Resp.RetailAmount
	orderInfo.Freight = pointy.GetPointer(moneyx.FormatDecimal(c.FinalFreight, 2))
	orderInfo.DiscountAmount = pointy.GetPointer(moneyx.FormatDecimal(c.ActivityAmount.Add(c.CouponAmount), 2))

	var finalAmount decimal.Decimal
	if b := decimal.NewFromFloat(0.01); c.FinalAmount.LessThan(b) {
		finalAmount = b
	} else {
		finalAmount = c.FinalAmount
	}
	finalAmount = finalAmount.Add(c.FinalFreight)
	finalAmountStr := moneyx.FormatDecimal(finalAmount, 2)
	orderInfo.ActualAmount = pointy.GetPointer(finalAmountStr)
	if c.OrderType != globalkey.OrderTypeVirtual {
		orderInfo.BalanceAmount = pointy.GetPointer("0")
		orderInfo.RemainingAmount = pointy.GetPointer(finalAmountStr)
		orderInfo.Gems =
			pointy.GetPointer(uint64(finalAmount.Mul(decimal.NewFromInt(1000)).IntPart() * int64(globalkey.GemRatio) / int64(1000)))
	}
	orderInfo.TradeState = pointy.GetPointer(globalkey.PaymentStatusNotPay)
	orderInfo.AutoComfirmDays = pointy.GetPointer(globalkey.AutoConfirmationDays)
	orderInfo.Remark = c.Req.Remark

	var exchangeLogIds []uint64
	var exchangeGoodsId uint64
	var exchangePoint string
	var productCode string
	var productName string
	var skuId uint64
	var skuCode string
	var sku string

	for _, v := range c.Resp.List {
		if v.SkuId != nil && v.Quantity != nil && *v.SkuId > 0 && *v.Quantity > 0 {
			if len(v.ExchangeLogIds) > 0 {
				exchangeLogIds = v.ExchangeLogIds
			}
			if v.ExchangeGoodsId != nil && *v.ExchangeGoodsId > 0 && v.ExchangePoint != nil {
				exchangeGoodsId = *v.ExchangeGoodsId
				exchangePoint = *v.ExchangePoint

				if v.ProductCode != nil && len(strings.TrimSpace(*v.ProductCode)) > 0 {
					productCode = *v.ProductCode
				}

				if v.Title != nil && len(strings.TrimSpace(*v.Title)) > 0 {
					productName = *v.Title
				}

				if v.SkuId != nil && *v.SkuId > 0 {
					skuId = *v.SkuId
				}

				if v.SkuCode != nil && len(strings.TrimSpace(*v.SkuCode)) > 0 {
					skuCode = *v.SkuCode
				}

				if v.Sku != nil && len(strings.TrimSpace(*v.Sku)) > 0 {
					sku = *v.Sku
				}
			}

			orderInfo.Skus = append(orderInfo.Skus, &orderclient.OrderSkuInfo{
				SkuId:          v.SkuId,
				SkuCode:        v.SkuCode,
				Sku:            v.Sku,
				ImageId:        v.MediaId,
				ImageUrl:       v.MediaUrl,
				ProductId:      v.ProductId,
				ProductCode:    v.ProductCode,
				PostId:         v.PostId,
				InviterId:      v.InviterId,
				EntryId:        v.EntryId,
				Title:          v.Title,
				Slug:           v.Slug,
				ProductType:    v.ProductType,
				BrandId:        v.BrandId,
				BrandTitle:     v.BrandTitle,
				BrandSlug:      v.BrandSlug,
				RetailPrice:    v.RetailPrice,
				Quantity:       v.Quantity,
				Weight:         v.Weight,
				RetailAmount:   v.SkuAmount,
				DiscountAmount: v.DiscountAmount,
				ActualAmount:   v.FinalAmount,
			})
		}
	}

	logrus.Info(fmt.Sprintf("CreateOrder exchangeLogIds: %+v", exchangeLogIds))
	logrus.Info(fmt.Sprintf("CreateOrder exchangeGoodsId: %+v", exchangeGoodsId))
	logrus.Info(fmt.Sprintf("CreateOrder exchangePoint: %+v", exchangePoint))

	if c.UserId == 10021 || c.UserId == 10035 { // 测试账号
		finalAmount = decimal.NewFromFloat(0.02)
	}

	var description string
	var count int
	if c.OrderType != globalkey.OrderTypeVirtual {
		for _, v := range c.Resp.List {
			if v.SkuId != nil && v.Quantity != nil && *v.SkuId > 0 && *v.Quantity > 0 {
				count += int(*v.Quantity)
				if len(description) == 0 && v.Title != nil {
					titleRune := []rune(strings.TrimSpace(*v.Title))
					if len(titleRune) > 38 {
						description = string(titleRune[0:38])
					} else {
						description = strings.TrimSpace(*v.Title)
					}
				}

				// skus = append(skus, &types.OrderSkuInfo{
				// 	Id:        v.Id,
				// 	ProductId: v.ProductId,
				// 	SkuId:     v.SkuId,
				// 	Quantity:  v.Quantity,
				// 	Title:     v.Title,
				// })
			}
		}
	}
	if len(c.Resp.List) > 1 && len(description) > 0 {
		description += fmt.Sprintf(" 等%d件商品", count)
	}
	var sn string
	var deferedUpdateOrderTradeState bool
	if c.PaymentType == globalkey.PaymentTypeWeChatPay { // 第三方支付-微信支付
		var paymentIsSet bool
		sn = uniqueidx.GenSn(uniqueidx.SN_PREFIX_THIRD_PART_PAYMENT)
		if c.Req.PaymentId != nil && *c.Req.PaymentId > 0 {
			// todo
		}
		if !paymentIsSet {
			orderInfo.Payments = append(orderInfo.Payments, &orderclient.OrderPaymentInfo{
				No:     &sn,
				Amount: pointy.GetPointer(moneyx.FormatDecimal(finalAmount, 2)),
				Type:   pointy.GetPointer(globalkey.PaymentTypeWeChatPay),
				Name:   pointy.GetPointer("微信支付"),
				Code:   pointy.GetPointer("WeChatPay"),
			})
		}
	} else if c.PaymentType == globalkey.PaymentTypeGems { // 余额支付
		var paymentIsSet bool
		sn = uniqueidx.GenSn(uniqueidx.SN_PREFIX_GEM)
		if c.Req.PaymentId != nil && *c.Req.PaymentId > 0 {
			// todo
		}
		if !paymentIsSet {
			orderInfo.Payments = append(orderInfo.Payments, &orderclient.OrderPaymentInfo{
				No:     &sn,
				Amount: pointy.GetPointer(moneyx.FormatDecimal(finalAmount, 2)),
				Type:   pointy.GetPointer(globalkey.PaymentTypeGems),
				Name:   pointy.GetPointer("宝石"),
				Code:   pointy.GetPointer("Gems"),
			})
			deferedUpdateOrderTradeState = true
		}
	}

	orderInfo.Addresses = append(orderInfo.Addresses, &orderclient.OrderAddressInfo{
		UserId:     pointy.GetPointer(c.UserId),
		Consignee:  c.Resp.Address.Consignee,
		Mobile:     c.Resp.Address.Mobile,
		Province:   c.Resp.Address.Province,
		City:       c.Resp.Address.City,
		District:   c.Resp.Address.District,
		Street:     c.Resp.Address.Street,
		Address:    c.Resp.Address.DetailedAddress,
		PostalCode: c.Resp.Address.PostalCode,
		RegionCode: c.Resp.Address.RegionCode,
		Label:      c.Resp.Address.Label,
	})

	for _, v := range c.Resp.ModifierList {
		orderInfo.Promotions = append(orderInfo.Promotions, &orderclient.OrderDiscountInfo{
			SkuId:            v.VariationId,
			ProductId:        v.ProductId,
			Title:            v.Title,
			Slug:             v.Slug,
			ActivityId:       v.ActivityId,
			ActivityRuleId:   v.RuleId,
			CouponTemplateId: v.CouponTemplateId,
			CouponId:         v.CouponId,
			CouponAmount:     v.CouponAmount,
			ActivityAmount:   v.ActivityAmount,
		})
	}

	// 2.3. 创建订单
	res, err := c.SvcCtx.OrderRpc.CreateOrder(c.Ctx, &orderInfo)
	if err != nil {
		h.createDeferedStockSubtractRevertTask(c, orderSn)
		return err
	}

	// logrus.Info(fmt.Sprintf("OrderHandler: %+v", res))

	finalAmountFen := moneyx.FormatDecimalToFen(finalAmount)
	//创建日志 冻结积分  (在这里冻结，因为下边修改状态createDeferedUpdateOrderTradeStateTask时候要扣减顺手带一件的积分，要用到point)
	err = h.freezePointCoupon(c, *res.Id, exchangeLogIds, exchangeGoodsId, exchangePoint,
		productCode, productName, skuId, skuCode, sku)
	if err != nil {
		return err
	}

	// 4 创建延时任务
	if deferedUpdateOrderTradeState {
		h.createDeferedUpdateOrderTradeStateTask(c, orderSn)
	}
	h.createDeferedCloseOrderTask(c, orderSn)

	// 4、5 获取支付参数、获取订阅模板
	c.Resp.CreateOrderResp = &types.CreateOrderResp{
		OrderId:   res.Id,
		OrderSn:   &orderSn,
		PayParams: h.getPayParams(c, sn, finalAmountFen, description),
		STID:      pointy.GetPointer(subscribex.ForumOrderShipmentTemplateID),
	}

	return nil
}

func (h *OrderHandler) createDeferedStockSubtractRevertTask(c *Context, orderSn string) (err error) {
	if len(c.Stocks) > 0 {
		// 4.3. 订单创建失败或被取消时，回退锁定库存
		var stocks []*mqueueTypes.ProductVariationStock
		for _, v := range c.Stocks {
			stocks = append(stocks, &mqueueTypes.ProductVariationStock{
				Id:    v.Id,
				Stock: v.Stock,
			})
		}
		task, err := h.newDeferedStockSubtractRevertTask(orderSn, stocks)
		if err != nil {
			logx.Errorw("create defered revert order stock task, json.Marshal failed", logx.Field("detail", err.Error()))
			logrus.Info(fmt.Sprintf("create defered revert order stock task, json.Marshal failed, err: %+v", err))
		} else {
			_, err = c.SvcCtx.AsynqClient.Enqueue(task)
			// info, err := c.SvcCtx.AsynqClient.Enqueue(task)
			if err != nil {
				logx.Errorw("create defered revert order stock task, could not schedule task", logx.Field("detail", err.Error()))
				logrus.Info(fmt.Sprintf("create defered revert order stock task, could not schedule task, err: %+v", err))
			}

			// logrus.Info(fmt.Sprintf("create defered revert order stock task, enqueued task: %+v", info))
		}
	}

	return
}

func (h *OrderHandler) newDeferedStockSubtractRevertTask(
	orderSn string,
	stocks []*mqueueTypes.ProductVariationStock,
) (*asynq.Task, error) {
	payload, err := json.Marshal(mqueueTypes.DeferedStockSubtractRevertPayload{
		Stocks: stocks,
	})
	if err != nil {
		return nil, err
	}

	return asynq.NewTask(mqueueTypes.DeferedStockSubtractRevert, payload, asynq.MaxRetry(3)), nil
}

func (h *OrderHandler) createDeferedUpdateOrderTradeStateTask(
	c *Context,
	orderSn string,
) (err error) {
	m := mqueuex.DeferedUpdateTradeStateMessage{
		OrderSn: pointy.GetPointer(orderSn),
	}
	body, err := json.Marshal(m)
	if err != nil {
		logx.Errorw("create defered update order tradeState task, json.Marshal failed", logx.Field("detail", err.Error()))
		logrus.Info(fmt.Sprintf("create defered update order tradeState task, json.Marshal failed, err: %+v", err))
	}
	return c.SvcCtx.KqDeferredUpdateTradeStateClient.Push(string(body))
}

func (h *OrderHandler) createDeferedCloseOrderTask(
	c *Context,
	orderSn string,
) (err error) {
	// 4.2. 创建延时关闭待支付订单任务
	// 4.2. Delayed closing of order tasks.
	task, err := h.newDeferedCloseOrderTask(orderSn)
	if err != nil {
		logx.Errorw("create defered close order task, json.Marshal failed", logx.Field("detail", err.Error()))
		logrus.Info(fmt.Sprintf("create defered close order task, json.Marshal failed, err: %+v", err))
	} else {
		_, err = c.SvcCtx.AsynqClient.Enqueue(
			// info, err := c.SvcCtx.AsynqClient.Enqueue(
			task,
			asynq.ProcessIn(time.Duration(globalkey.CloseOrderTimeoutMinutes)*time.Minute),
		)
		if err != nil {
			logx.Errorw("create defered close order task, could not schedule task", logx.Field("detail", err.Error()))
			logrus.Info(fmt.Sprintf("create defered close order task, could not schedule task, err: %+v", err))
		}

		// logrus.Info(fmt.Sprintf("create defered close order task, enqueued task: %+v", info))
	}

	return
}

func (h *OrderHandler) newDeferedCloseOrderTask(
	orderSn string,
) (*asynq.Task, error) {
	payload, err := json.Marshal(mqueueTypes.DeferedCloseOrderPayload{
		OrderSn: orderSn,
	})
	if err != nil {
		return nil, err
	}
	return asynq.NewTask(mqueueTypes.DeferedCloseOrder, payload, asynq.MaxRetry(3)), nil
	// task options can be passed to NewTask, which can be overridden at enqueue time.
	// Options include MaxRetry, Queue, Timeout, Deadline, Unique etc.
	// return asynq.NewTask(mqueueTypes.DeferedCloseOrder, payload, asynq.MaxRetry(5), asynq.Timeout(20 * time.Minute)), nil
}

func (h *OrderHandler) getPayParams(
	c *Context,
	sn string,
	amount int64,
	description string,
) (payParams *types.ThirdPartyPaymentInfo) {
	// 4. 获取支付参数
	if c.Req.PaymentId == nil || *c.Req.PaymentId == uint64(globalkey.PaymentTypeWeChatPay) {
		payParams = &types.ThirdPartyPaymentInfo{}
		wxPayInfo, err := h.getWxPayParams(c, sn, amount, c.UserOpenId, description)
		if err == nil {
			payParams.WxPayInfo = wxPayInfo
		}
	}

	return
}

func (h *OrderHandler) getWxPayParams(
	c *Context,
	sn string,
	amount int64,
	openId string,
	description string,
) (*types.ThirdPartyPaymentWxPayInfo, error) {
	res, err := h.createWxPrePayOrder(c, sn, amount, openId, description)
	if err != nil {
		return nil, err
	}

	return &types.ThirdPartyPaymentWxPayInfo{
		Appid:     pointy.GetPointer(c.SvcCtx.Config.WxMiniConf.AppId),
		NonceStr:  res.NonceStr,
		PaySign:   res.PaySign,
		Package:   res.Package,
		Timestamp: res.TimeStamp,
		SignType:  res.SignType,
	}, nil
}

func (h *OrderHandler) createWxPrePayOrder(
	c *Context,
	sn string,
	amount int64,
	openId string,
	description string,
) (*jsapi.PrepayWithRequestPaymentResponse, error) {
	wxPayClient, err := svc.NewWxPayClientV3(c.SvcCtx.Config)
	if err != nil {
		logx.Errorw("create wechat pay prepayorder, init failed", logx.Field("detail", err.Error()))
		// ErrCode: 106002, ErrMsg: 微信支付初始化失败-初始化客户端错误
		// logrus.Info(fmt.Sprintf("create wechat pay prepayorder init err: %+v", err))
		// error http response:[StatusCode: 400 Code: \"PARAM_ERROR\"\nMessage: 输入源“/body/amount/total”映射到数值字段“总金额”规则校验失败，值低于最小值 1\nDetail:\n{\n  \"location\": \"body\",\n  \"value\": 0\n}\nHeader:\n - Date=[Sat, 13 May 2023 10:27:21 GMT]\n - Cache-Control=[no-cache, must-revalidate]\n - X-Content-Type-Options=[nosniff]\n - Connection=[keep-alive]\n - Keep-Alive=[timeout=8]\n - Request-Id=[0889CCFDA20610D70318B6BB8C582091F70328CBDC04-268435464]\n - Content-Language=[zh-CN]\n - Server=[nginx]\n - Content-Type=[application/json; charset=utf-8]\n - Content-Length=[184]]
		return nil, resultx.NewErrCode(resultx.PAYMENT_WECHAT_PAY_INIT_PRE_ORDER_FAIL)
	}

	jsApiSvc := jsapi.JsapiApiService{
		Client: wxPayClient,
	}

	// Get the prepay_id, as well as the parameters and signatures needed to invoke the payment
	res, _, err := jsApiSvc.PrepayWithRequestPayment(c.Ctx, jsapi.PrepayRequest{
		Appid:       core.String(c.SvcCtx.Config.WxMiniConf.AppId),
		Mchid:       core.String(c.SvcCtx.Config.WxPayConf.MchId),
		Description: core.String(description),
		OutTradeNo:  core.String(sn),
		Attach:      core.String(description),
		NotifyUrl:   core.String(c.SvcCtx.Config.WxPayConf.NotifyUrl),
		Amount: &jsapi.Amount{
			Total: core.Int64(amount),
		},
		Payer: &jsapi.Payer{
			Openid: core.String(openId),
		},
	})
	if err != nil {
		logx.Errorw("create wechat pay prepayorder failed", logx.Field("detail", err.Error()))
		// logrus.Info(fmt.Sprintf("create wechat pay prepayorder err: %+v", err))
		// Post "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi": dial tcp [240e:e1:aa00:4000::94]:443: connect: cannot assign requested address"
		return nil, resultx.NewErrCode(resultx.PAYMENT_WECHAT_PAY_INIT_PRE_ORDER_FAIL)
	}

	return res, nil
}

func (h *OrderHandler) freezePointCoupon(
	c *Context,
	oid uint64,
	exchangeLogIds []uint64,
	exchangeGoodsId uint64,
	exchangePoint string,
	productCode string,
	productName string,
	skuId uint64,
	skuCode string,
	sku string,
) (err error) {
	if exchangeGoodsId > 0 && len(strings.TrimSpace(exchangePoint)) > 0 {
		//添加TransactionLog并冻结
		log, err := c.SvcCtx.UserRpc.CreateIntegralTransactionLog(c.Ctx, &userclient.IntegralTransactionLogInfo{
			UserId:      pointy.GetPointer(c.UserId),
			UserName:    pointy.GetPointer(c.UserName),
			ProductId:   pointy.GetPointer(exchangeGoodsId),
			ProductCode: pointy.GetPointer(productCode),
			ProductName: pointy.GetPointer(productName),
			SkuId:       pointy.GetPointer(skuId),
			SkuCode:     pointy.GetPointer(skuCode),
			Sku:         pointy.GetPointer(sku),
			OrderId:     c.Resp.CreateOrderResp.OrderId,
			OrderSn:     c.Resp.CreateOrderResp.OrderSn,
			Points:      pointy.GetPointer(exchangePoint),
			Status:      pointy.GetPointer(uint32(globalkey.StatusExpired)), //这里用作冻结
		})
		if err != nil {
			return err
		}
		if log.Id != nil && *log.Id > 0 {
			exchangeLogIds = append(exchangeLogIds, *log.Id)
		}
	}
	//UserKey: 优惠券id
	userKey := h.getUserUsedCouponKey(c.UserId)
	orderKey := h.getOrderFrozenKey(oid)
	var orderValue string
	var couponIds []string
	var presentCouponIds []string
	var activityIds []string
	//查看有没有已经被冻结的优惠券  将已经冻结的和本次冻结的优惠券合并起来
	userRes, err := c.SvcCtx.BizRedis.GetCtx(c.Ctx, userKey)
	if err != nil {
		return err
	}
	if len(strings.TrimSpace(userRes)) > 0 {
		for _, id := range strings.Split(userRes, ",") {
			if len(strings.TrimSpace(id)) > 0 {
				couponIds = append(couponIds, id)
			}
		}
	}
	if len(c.Resp.CouponList) > 0 {
		for _, v := range c.Resp.CouponList {
			if v.Id != nil && *v.Id > 0 {
				couponIds = append(couponIds, strconv.FormatUint(*v.Id, 10))
				presentCouponIds = append(presentCouponIds, strconv.FormatUint(*v.Id, 10))
			}
		}
		if len(presentCouponIds) > 0 {
			orderValue = strings.Join(presentCouponIds, ",")
		} else {
			orderValue = "0"
		}
	} else {
		orderValue = "0"
	}

	//OrderKey: 优惠券id:优惠活动id:logids:goodsId:积分
	if len(c.Resp.ActivityList) > 0 {
		for _, v := range c.Resp.ActivityList {
			if v.Id != nil && *v.Id > 0 {
				activityIds = append(activityIds, strconv.FormatUint(*v.Id, 10))
			}
		}
		if len(activityIds) > 0 {
			orderValue += ":" + strings.Join(activityIds, ",")
		} else {
			orderValue = ":" + "0"
		}
	} else {
		orderValue += ":" + "0"
	}

	if len(exchangeLogIds) > 0 {
		var ids []string
		for _, id := range exchangeLogIds {
			ids = append(ids, strconv.FormatUint(id, 10))
		}
		orderValue += ":" + strings.Join(ids, ",")
	} else {
		orderValue += ":" + "0"
	}

	if exchangeGoodsId > 0 {
		orderValue += ":" + strconv.FormatUint(exchangeGoodsId, 10)
	} else {
		orderValue += ":" + "0"
	}

	if len(strings.TrimSpace(exchangePoint)) > 0 {
		orderValue += ":" + exchangePoint
	} else {
		orderValue += ":" + "0"
	}

	err = c.SvcCtx.BizRedis.SetCtx(c.Ctx, orderKey, orderValue)
	if err != nil {
		return err
	}

	if len(couponIds) > 0 {
		err = c.SvcCtx.BizRedis.SetCtx(c.Ctx, userKey, strings.Join(couponIds, ","))
		if err != nil {
			return err
		}
	}

	return nil
}

func (h *OrderHandler) getUserUsedCouponKey(uid uint64) string {
	return fmt.Sprintf("{%s}:%d", cachex.UserUsedCoupon, uid)
}

func (h *OrderHandler) getOrderFrozenKey(oid uint64) string {
	return fmt.Sprintf("{%s}:%d", cachex.OrderFrozen, oid)
}
